package com.jerome.fs.server.aggregation.handler;

import com.jerome.fs.constants.AppConstant;
import com.jerome.fs.entities.WeatherVo;
import com.jerome.fs.http.HttpConstant;
import com.jerome.fs.http.HttpMessageParser;
import com.jerome.fs.http.HttpMethodEnum;
import com.jerome.fs.http.HttpStatus;
import com.jerome.fs.http.Request;
import com.jerome.fs.http.Response;
import com.jerome.fs.http.server.HttpHandler;
import com.jerome.fs.server.aggregation.ContextUtil;
import com.jerome.fs.server.aggregation.ServerMqEvent;
import com.jerome.fs.server.aggregation.constants.AggregationConstant;
import com.jerome.fs.sync.CenterServer;
import com.jerome.fs.util.JsonUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * Aggregation Server HTTP Handler
 *
 * @author Jerome
 * @date 2023/9/5
 */
public class AggregationHttpHandler implements HttpHandler {

    @Override
    public void handle(Socket socket) {
        try {
            try {
                // Parse the HTTP request from the input stream of the socket
                Request httpRequest = HttpMessageParser.parse2request(socket.getInputStream());
                HttpMethodEnum methodEnum = HttpMethodEnum.getByCode(httpRequest.getMethod());
                switch (methodEnum) {
                    case REGISTER:
                    case PINGPONG:
                    case SYNC:
                    case INCREMENT_SYNC:
                        //走主从同步逻辑和心跳逻辑
                        // socket长连接
                        new CenterServer(socket, new AggregationCenterServerHandler());
                        break;
                    case PUT:
                        // Handle PUT request
                        if (!httpRequest.getUri().matches(AggregationConstant.PUT_PATH)) {
                            throw new Exception("you need to PUT /weather");
                        } else if (!ContextUtil.getAggregationConfig().getMode().equals(AggregationConstant.MASTER)) {
                            //only master support PUT
                            throw new Exception("only master support PUT");
                        }
                        handlePutRequest(httpRequest, socket);
                        break;
                    case GET:
                        if (!httpRequest.getUri().matches(AggregationConstant.GET_PATH)) {
                            throw new Exception("you need to GET /weather/{id}");
                        }
                        // Handle GET request
                        handleGetRequest(httpRequest, socket);
                        break;
                    default:
                        break;
                }
            } catch (Exception e) {
                // If an exception occurs, build an HTTP response containing the exception information
                OutputStream outputStream = socket.getOutputStream();
                PrintWriter out = new PrintWriter(outputStream);
                Response response = HttpMessageParser.buildErrorResponse(e);
                response.getHeaders().put("Content-Type", "application/json");
                String httpRes = HttpMessageParser.buildHttpResponse(response);
                out.print(httpRes);
                // Flush the output stream to ensure the response message is sent
                out.flush();
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * put 请求
     *
     * @param request
     * @param socket
     * @author Jerome
     * @date 2023/9/12 20:06
     */
    private void handlePutRequest(Request request, Socket socket) throws Exception {
        int scLastTime = getRequestLastTime(request);
        // Update the local clock
        int latestTime = ContextUtil.getLamportClock().tick(scLastTime);

        // Read the content of the PUT request
        // Sending no content to the server should cause a 204 status code to be returned.
        if (request.getMessage() == null || "".equals(request.getMessage().trim())) {
            Response response = new Response();
            response.setVersion(HttpConstant.HTTP_VERSION);
            response.setCode(HttpStatus.HTTP_NO_CONTENT);
            response.setStatus(HttpStatus.msg(response.getCode()));
            response.getHeaders().put(AppConstant.LAMPORT_HEADER_NAME, latestTime + "");
            response.setMessage("no content");
            PrintWriter out = new PrintWriter(socket.getOutputStream());
            // Build an HTTP response based on the request and result
            String httpRes = HttpMessageParser.buildHttpResponse(response);
            out.print(httpRes);
            out.flush();
            socket.close();
            return;
        }
        WeatherVo weatherVo = JsonUtil.readValue(request.getMessage(), WeatherVo.class);
        // Check if the data is valid
        // Finally, if
        // the JSON data does not make sense (incorrect JSON) you may return status code 500 - Internal
        // server error.
        if (weatherVo.getId() == null || "".equals(weatherVo.getId().trim())) {
            Response response = new Response();
            response.setVersion(HttpConstant.HTTP_VERSION);
            response.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
            response.setStatus(HttpStatus.msg(response.getCode()));
            response.getHeaders().put(AppConstant.LAMPORT_HEADER_NAME, latestTime + "");
            response.setMessage("Incorrect JSON");
            PrintWriter out = new PrintWriter(socket.getOutputStream());
            // Build an HTTP response based on the request and result
            String httpRes = HttpMessageParser.buildHttpResponse(response);
            out.print(httpRes);
            out.flush();
            socket.close();
            return;
        }
        // Put it in the queue
        weatherVo.setLatestTime(scLastTime);
        ServerMqEvent event = new ServerMqEvent(scLastTime, socket, request, weatherVo.getId(), weatherVo);
        // Put it in the queue
        ContextUtil.getMq().publish(weatherVo.getId(), event);
    }

    /**
     * get 请求
     *
     * @param request
     * @param socket
     * @author Jerome
     * @date 2023/9/12 20:07
     */
    private void handleGetRequest(Request request, Socket socket) throws Exception {
        // Get the ID
        String path = request.getUri().split("\\?")[0];
        String id = path.split("/")[2];
        // Put it in the queue
        ServerMqEvent event = new ServerMqEvent(ContextUtil.getLamportClock().getLatestTime(), socket, request, id, null);
        ContextUtil.getMq().publish(id, event);
    }

    /**
     * 获取请求 lamport 时钟
     *
     * @param request
     * @author Jerome
     * @date 2023/9/12 20:06
     */
    private int getRequestLastTime(Request request) throws IllegalAccessException {
        //Get the Lamport clock of the content server
        String csClockStr = request.getHeaders().get(AppConstant.LAMPORT_HEADER_NAME);
        if (csClockStr == null) {
            throw new IllegalAccessException("Header does not have Last-Time");
        }
        return Integer.parseInt(csClockStr);
    }
}
